{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib\n",
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import scipy.io\n",
    "import os"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import claude.utils as cu\n",
    "import claude.tx as tx\n",
    "import claude.claudeflow.systems as cfs\n",
    "import claude.claudeflow.training as cft\n",
    "import claude.claudeflow.helper as cfh\n",
    "import claude.claudeflow.autoencoder as ae\n",
    "import claude.claudeflow.models.SSFstatic as ssf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# True:  runs on cluster with GPU etc\n",
    "# False: can run localy\n",
    "runWithNonlinear = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See [`tf_wdmSystem-learning.html`](tf_wdmSystem-learning.html) for version with `runWithNonlinear = True`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "if runWithNonlinear:  \n",
    "    # load memory_saving_gradients.py\n",
    "    # see https://github.com/cybertronai/gradient-checkpointing\n",
    "    import urllib.request\n",
    "    memSavingGradientsFilename = \"memory_saving_gradients.py\"\n",
    "    if not os.path.exists(memSavingGradientsFilename):\n",
    "        memSavingGradientsURL = \"https://raw.githubusercontent.com/cybertronai/gradient-checkpointing/master/memory_saving_gradients.py\"\n",
    "        urllib.request.urlretrieve(memSavingGradientsURL, memSavingGradientsFilename)\n",
    "\n",
    "        import memory_saving_gradients\n",
    "        tf.__dict__[\"gradients\"] = memory_saving_gradients.gradients_collection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "M:\t64\n",
      "nPol:\t2\n",
      "sps:\t16\n",
      "nSamples:\t1024\n",
      "rollOff:\t0.05\n",
      "filterSpan:\t128\n",
      "optimizeP:\tFalse\n",
      "PdBm:\t-2\n",
      "Rs:\t32000000000.0\n",
      "channels:\t[-100.  -50.    0.   50.  100.]\n",
      "nChannels:\t5\n",
      "frequencyShift:\tTrue\n",
      "dispersionCompensation:\tFalse\n",
      "beta2:\t2.0999953937404486e-26\n",
      "dz:\t1000000.0\n",
      "Fs:\t512000000000.0\n",
      "N:\t16384\n",
      "realType:\t<dtype: 'float64'>\n",
      "complexType:\t<dtype: 'complex128'>\n",
      "nPilots:\t128\n",
      "pilotOrder:\t4\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Tx\n",
    "param = cfs.defaultParameters(precision='double')\n",
    "\n",
    "if runWithNonlinear:    \n",
    "    param.sps = 16\n",
    "    param.nSamples = 1024\n",
    "    param.optimizeP = False\n",
    "    param.PdBm  = 1\n",
    "else:    \n",
    "    param.sps = 16\n",
    "    param.nSamples = 1024\n",
    "    param.PdBm  = -2 # linear regime such there is no nonlinear interference\n",
    "    \n",
    "param.M     = 64\n",
    "param.Fs    = param.sps * param.Rs\n",
    "param.nPilots = 128\n",
    "param.pilotOrder = 4\n",
    "print(param)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fs:\t512000000000.0\n",
      "N:\t18432\n",
      "nSteps:\t2\n",
      "stepSize:\t100\n",
      "ampScheme:\tEDFA\n",
      "noiseEnabled:\tTrue\n",
      "manakovEnabled:\tTrue\n",
      "dispersionCompensationEnabled:\tFalse\n",
      "checkpointInverval:\t2\n",
      "nPol:\t2\n",
      "lambda_:\t1.55003597538907e-06\n",
      "Fc:\t193410000000000.0\n",
      "D:\t16.464\n",
      "alpha:\t0.2\n",
      "beta2:\t2.0999953937404486e-26\n",
      "gamma:\t0\n",
      "nSpans:\t10\n",
      "spanLength:\t100\n",
      "noiseFigure:\t5\n",
      "intType:\t<dtype: 'int32'>\n",
      "realType:\t<dtype: 'float64'>\n",
      "complexType:\t<dtype: 'complex128'>\n",
      "stepSizeTemplate:\t[ 7.52466421 92.47533579]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "ssfParam = ssf.defaultParameters(precision='double')\n",
    "ssfParam.Fs = param.Fs\n",
    "ssfParam.N = param.sps * (param.nSamples+param.nPilots)\n",
    "ssfParam.noiseEnabled = True\n",
    "ssfParam.noiseFigure = 5\n",
    "ssfParam.nSpans = 10\n",
    "\n",
    "if runWithNonlinear:\n",
    "    ssfParam.gamma = 1.3\n",
    "    ssfParam.nSteps = 350\n",
    "else:\n",
    "    ssfParam.gamma = 0\n",
    "    ssfParam.nSteps = 2\n",
    "\n",
    "# ssfParam.stepSize = ssfParam.spanLength/ssfParam.nSteps\n",
    "# ssfParam.stepSizeTemplate = ssfParam.stepSize * np.ones((ssfParam.nSteps,)) # constant stepsize\n",
    "\n",
    "ssfParam.stepSizeTemplate = ssf.logStepSizes(ssfParam.spanLength, ssfParam.alpha, ssfParam.nSteps) # log stepsize\n",
    "\n",
    "print(ssfParam)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "seed:\t1337\n",
      "constellationOrder:\t64\n",
      "constellationDim:\t2\n",
      "nLayers:\t2\n",
      "nHidden:\t32\n",
      "activation:\t<function relu at 0x7f03ce2d0e18>\n",
      "\n"
     ]
    }
   ],
   "source": [
    "aeParam = cu.AttrDict()\n",
    "aeParam.seed    = 1337\n",
    "aeParam.constellationOrder = param.M\n",
    "aeParam.constellationDim = 2\n",
    "\n",
    "if runWithNonlinear:\n",
    "    aeParam.nLayers = 4\n",
    "    aeParam.nHidden = 64\n",
    "else:\n",
    "    aeParam.nLayers = 2\n",
    "    aeParam.nHidden = 32\n",
    "\n",
    "aeParam.activation = tf.nn.relu\n",
    "\n",
    "print(aeParam)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "M=64,learningRate=0.005,seed=1337\n",
      "batchSize:\t1\n",
      "nBatches:\t2\n",
      "nMiniBatches:\t4\n",
      "evalBatches:\t5\n",
      "removeSymbols:\t32\n",
      "learningRate:\t0.005\n",
      "nEpochs:\t100\n",
      "displayStep:\t5\n",
      "path:\tresults_SSFM\n",
      "filename:\tM064_seed1337\n",
      "earlyStopping:\t10\n",
      "earlyStoppingMetric:\txentropy\n",
      "summaries:\tTrue\n",
      "summaryString:\tM=64,learningRate=0.005,seed=1337\n",
      "\n"
     ]
    }
   ],
   "source": [
    "trainingParam = cu.AttrDict()\n",
    "if runWithNonlinear:    \n",
    "    trainingParam.batchSize     = 1\n",
    "    trainingParam.nBatches      = 2\n",
    "    trainingParam.nMiniBatches  = 4\n",
    "    trainingParam.evalBatches   = 10\n",
    "    trainingParam.removeSymbols = 32\n",
    "    trainingParam.learningRate  = 0.0005\n",
    "else:    \n",
    "    trainingParam.batchSize     = 1\n",
    "    trainingParam.nBatches      = 2\n",
    "    trainingParam.nMiniBatches  = 4\n",
    "    trainingParam.evalBatches   = 5\n",
    "    trainingParam.removeSymbols = 32\n",
    "    trainingParam.learningRate  = 0.005\n",
    "    \n",
    "trainingParam.nEpochs             = 100\n",
    "\n",
    "trainingParam.displayStep         = 5\n",
    "trainingParam.path                = 'results_SSFM'\n",
    "trainingParam.filename            = 'M{:03d}_seed{:04d}'.format(param.M,aeParam.seed)\n",
    "trainingParam.earlyStopping       = 10\n",
    "trainingParam.earlyStoppingMetric = 'xentropy'\n",
    "trainingParam.summaries           = True\n",
    "\n",
    "# generalize this\n",
    "if trainingParam.summaries:\n",
    "    # tensorboard directory\n",
    "    hyperParam = ['M']\n",
    "    aeHyperParam = ['seed']\n",
    "    trainingHyperParam = ['learningRate']\n",
    "\n",
    "    trainingParam.summaryString = ','.join(  [ '{}={}'.format(item,param[item]) for item in hyperParam ]\n",
    "                                            +[ '{}={}'.format(item,trainingParam[item]) for item in trainingHyperParam ]\n",
    "                                            +[ '{}={}'.format(item,aeParam[item]) for item in aeHyperParam ] )\n",
    "\n",
    "    print(trainingParam.summaryString,flush=True)\n",
    "    \n",
    "print(trainingParam)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Constants\n",
    "c = 299792458\n",
    "tf.set_random_seed(aeParam.seed)\n",
    "np.random.seed(aeParam.seed)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING: Logging before flag parsing goes to stderr.\n",
      "W1013 15:36:30.858669 139655415179072 deprecation.py:506] From /home/rasmus/.conda/envs/claudeOnline/lib/python3.6/site-packages/tensorflow/python/ops/init_ops.py:1251: calling VarianceScaling.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Call initializer instance with the dtype argument instead of passing it to the constructor\n"
     ]
    }
   ],
   "source": [
    "X = tf.placeholder(param.realType, shape=(None, param.nChannels, param.nPol, param.nSamples, param.M))\n",
    "txSymbols, constellation = ae.encoder(X, aeParam, toComplex=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# pilot symbols\n",
    "X_pilot = tf.placeholder(param.realType, shape=(None, param.nChannels, param.nPol, param.nPilots, param.pilotOrder))\n",
    "pilot_constellation = tx.qammod(param.pilotOrder)\n",
    "pilot_constellation_tf = tf.constant(pilot_constellation, param.complexType)\n",
    "pilot_txSymbols = cfh.QAMencoder(X_pilot, pilot_constellation_tf, realOutput=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "txSymbols = tf.concat([pilot_txSymbols,txSymbols], axis=-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tf.Tensor 'concat:0' shape=(?, 5, 2, 1152) dtype=complex128>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "txSymbols"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "param.nSamples = param.nSamples + param.nPilots\n",
    "signal = cfs.wdmTransmitter(txSymbols, param)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "nSpans: 10, nSteps: 2\n"
     ]
    }
   ],
   "source": [
    "ssfParam.dispersionCompensationEnabled = False\n",
    "ssfParam.stepSize = tf.placeholder(param.realType, shape=(ssfParam.nSpans, ssfParam.nSteps))\n",
    "signalOut = ssf.model(ssfParam, signal)\n",
    "\n",
    "param.dispersionCompensation = True\n",
    "param.beta2  = ssfParam.D * 1e-6 * ( c / ssfParam.Fc )**2 / ( 2 * np.pi * c )\n",
    "param.dz     = ssfParam.spanLength * 1e3 * ssfParam.nSpans\n",
    "rxSymbols = cfs.wdmReceiver(signalOut, param)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "P0 = tf.get_default_graph().get_tensor_by_name(\"P0:0\")\n",
    "PdBm = tf.get_default_graph().get_tensor_by_name(\"PdBm:0\")\n",
    "normP0 = tf.get_default_graph().get_tensor_by_name(\"normP0:0\")\n",
    "rxSymbols = tf.cast( tf.rsqrt( normP0 ), param.complexType ) * rxSymbols"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# simulate static phase rotation \n",
    "if not runWithNonlinear:\n",
    "    rxSymbols = rxSymbols * tf.exp(1j * 0.5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "rxSymbols = cfh.staticPhaseRotationCompensation(rxSymbols, nPilots=param.nPilots)\n",
    "rxSymbols = cfh.testPhases(pilot_constellation_tf, txSymbols, rxSymbols, 4, param.pilotOrder, nTestPhases=4, nPilots=param.nPilots)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "txSymbols = txSymbols[..., param.nPilots:]\n",
    "rxSymbols = rxSymbols[..., param.nPilots:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "rxSymbols, txSymbols, labels = cfh.truncate(trainingParam.removeSymbols, rxSymbols, txSymbols, tf.linalg.matrix_transpose(X))\n",
    "labels = tf.linalg.matrix_transpose(labels)\n",
    "\n",
    "decoder = ae.decoder( rxSymbols, aeParam, fromComplex=True)\n",
    "softmax = tf.nn.softmax(decoder)\n",
    "\n",
    "# loss\n",
    "loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=labels, logits=decoder))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "nDims = 4 # Dimension of X\n",
    "reduce_axis = [0, 2, 3]\n",
    "errorrate = cfh.symbolErrorrate(constellation, txSymbols, rxSymbols, nDims, param.M, reduce_axis)\n",
    "\n",
    "one = tf.constant( 1, P0.dtype )\n",
    "effSNR = cfh.effectiveSNR(txSymbols, rxSymbols, one, reduce_axis)\n",
    "\n",
    "# gaussMI\n",
    "gaussMI = []\n",
    "for (txChannel, rxChannel) in zip(tf.split(txSymbols, param.nChannels, axis=1), tf.split(rxSymbols, param.nChannels, axis=1)):\n",
    "    gaussMI_temp = cfh.gaussianMI(tf.reshape(txChannel,[-1]), tf.reshape(rxChannel,[-1]), constellation, param.M)\n",
    "    gaussMI.append(gaussMI_temp)\n",
    "gaussMI = tf.stack(gaussMI)\n",
    "\n",
    "# nnMI\n",
    "Px = tf.constant( 1/param.M, param.realType )\n",
    "nnMI = []\n",
    "for (sm, l) in zip(tf.split(softmax, param.nChannels, axis=1), tf.split(labels, param.nChannels, axis=1)):\n",
    "    nnMI_temp = cfh.softmaxMI(sm, l, Px)\n",
    "    nnMI.append(nnMI_temp)\n",
    "nnMI = tf.stack(nnMI)\n",
    "\n",
    "# only gaussMId channel\n",
    "gaussMIdChannel = param.nChannels//2 +1\n",
    "errorrate = errorrate[gaussMIdChannel]\n",
    "gaussMI = gaussMI[gaussMIdChannel]\n",
    "effSNR = effSNR[gaussMIdChannel]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "metricsDict = {'xentropy':loss, 'errorrate':errorrate, 'gaussMI': gaussMI, 'nnMI': nnMI, 'effSNR': effSNR}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "def feedDictFun(trainingParam):\n",
    "    randomisedStepSize = ssf.randomizeSteps(ssfParam.stepSizeTemplate, ssfParam.spanLength, ssfParam.nSpans)\n",
    "    x, idx, x_seed = cu.hotOnes((trainingParam.batchSize, param.nChannels, param.nPol, param.nSamples-param.nPilots), (1,2,3,4,0),param.M)\n",
    "    x_pilot, _, _  = cu.hotOnes((trainingParam.batchSize, param.nChannels, param.nPol, param.nPilots), (1,2,3,4,0),param.pilotOrder)\n",
    "    return { X:x, X_pilot:x_pilot, ssfParam.stepSize:randomisedStepSize }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "W1013 15:36:49.354468 139655415179072 lazy_loader.py:50] \n",
      "The TensorFlow contrib module will not be included in TensorFlow 2.0.\n",
      "For more information, please see:\n",
      "  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md\n",
      "  * https://github.com/tensorflow/addons\n",
      "  * https://github.com/tensorflow/io (for I/O related ops)\n",
      "If you depend on functionality not listed there, please file an issue.\n",
      "\n",
      "W1013 15:36:49.505682 139655415179072 deprecation.py:323] From /home/rasmus/PhD/claudeOnline/claude/claude/claudeflow/training.py:26: Variable.initialized_value (from tensorflow.python.ops.variables) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use Variable.read_value. Variables in 2.X are initialized automatically both in eager and graph (inside tf.defun) contexts.\n",
      "W1013 15:36:58.000062 139655415179072 deprecation.py:323] From /home/rasmus/.conda/envs/claudeOnline/lib/python3.6/site-packages/tensorflow/python/ops/math_grad.py:1250: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use tf.where in 2.0, which has the same broadcast rule as np.where\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 0005 - xentropy: 3.9725 - errorrate: 0.4042 - gaussMI: 4.5424 - nnMI: 0.2689 - effSNR: 16.8626\n",
      "epoch: 0010 - xentropy: 3.6043 - errorrate: 0.3795 - gaussMI: 4.6195 - nnMI: 0.8001 - effSNR: 16.8560\n",
      "epoch: 0015 - xentropy: 3.1596 - errorrate: 0.3921 - gaussMI: 4.6193 - nnMI: 1.4416 - effSNR: 16.8553\n",
      "epoch: 0020 - xentropy: 2.7683 - errorrate: 0.3669 - gaussMI: 4.7443 - nnMI: 2.0062 - effSNR: 16.8670\n",
      "epoch: 0025 - xentropy: 2.4229 - errorrate: 0.3503 - gaussMI: 4.8132 - nnMI: 2.5045 - effSNR: 16.8213\n",
      "epoch: 0030 - xentropy: 2.1527 - errorrate: 0.3242 - gaussMI: 4.9014 - nnMI: 2.8943 - effSNR: 16.8529\n",
      "epoch: 0035 - xentropy: 1.9142 - errorrate: 0.3015 - gaussMI: 4.9906 - nnMI: 3.2385 - effSNR: 16.7981\n",
      "epoch: 0040 - xentropy: 1.6991 - errorrate: 0.2789 - gaussMI: 5.0432 - nnMI: 3.5487 - effSNR: 16.8793\n",
      "epoch: 0045 - xentropy: 1.4928 - errorrate: 0.2758 - gaussMI: 5.0701 - nnMI: 3.8464 - effSNR: 16.9136\n",
      "epoch: 0050 - xentropy: 1.3098 - errorrate: 0.2824 - gaussMI: 5.0536 - nnMI: 4.1104 - effSNR: 16.8150\n",
      "epoch: 0055 - xentropy: 1.1539 - errorrate: 0.2553 - gaussMI: 5.1105 - nnMI: 4.3352 - effSNR: 16.8466\n",
      "epoch: 0060 - xentropy: 1.0247 - errorrate: 0.2470 - gaussMI: 5.1297 - nnMI: 4.5217 - effSNR: 16.8423\n",
      "epoch: 0065 - xentropy: 0.9225 - errorrate: 0.2335 - gaussMI: 5.1720 - nnMI: 4.6692 - effSNR: 16.9016\n",
      "epoch: 0070 - xentropy: 0.8452 - errorrate: 0.2335 - gaussMI: 5.1724 - nnMI: 4.7807 - effSNR: 16.8424\n",
      "epoch: 0075 - xentropy: 0.7881 - errorrate: 0.2158 - gaussMI: 5.2138 - nnMI: 4.8630 - effSNR: 16.8868\n",
      "epoch: 0080 - xentropy: 0.7462 - errorrate: 0.2059 - gaussMI: 5.2284 - nnMI: 4.9235 - effSNR: 16.8728\n",
      "epoch: 0085 - xentropy: 0.7014 - errorrate: 0.2074 - gaussMI: 5.2440 - nnMI: 4.9880 - effSNR: 16.8927\n",
      "epoch: 0090 - xentropy: 0.6628 - errorrate: 0.1975 - gaussMI: 5.2580 - nnMI: 5.0438 - effSNR: 16.9037\n",
      "epoch: 0095 - xentropy: 0.6294 - errorrate: 0.1990 - gaussMI: 5.2585 - nnMI: 5.0920 - effSNR: 16.8525\n",
      "epoch: 0100 - xentropy: 0.6114 - errorrate: 0.1941 - gaussMI: 5.2605 - nnMI: 5.1180 - effSNR: 16.8485\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "W1013 17:11:25.905244 139655415179072 deprecation.py:323] From /home/rasmus/.conda/envs/claudeOnline/lib/python3.6/site-packages/tensorflow/python/training/saver.py:1276: checkpoint_exists (from tensorflow.python.training.checkpoint_management) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Use standard file APIs to check for files with this prefix.\n"
     ]
    }
   ],
   "source": [
    "sess = tf.compat.v1.Session()\n",
    "sess, outMetrics = cft.train(sess, tf.train.AdamOptimizer, loss, metricsDict, trainingParam, feedDictFun, debug=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "outPdBm = sess.run( PdBm )\n",
    "\n",
    "np_constellation = sess.run(constellation)\n",
    "plt.plot(np.real(np_constellation),np.imag(np_constellation),'.')\n",
    "plt.axis('square')\n",
    "lim_ = 1.5\n",
    "plt.xlim(-lim_,lim_)\n",
    "plt.ylim(-lim_,lim_)\n",
    "plt.savefig('const.png')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "import claude.models.NLIN as nlin\n",
    "# System parameters\n",
    "qamParam = nlin.defaultParameters()\n",
    "qamParam.nSpans = ssfParam.nSpans\n",
    "\n",
    "aseNoisePower, interConst, intraConst, interConstAdd, intraConstAdd = nlin.calcConstants(qamParam)\n",
    "powerSweep = np.arange(-8, 4, 0.1)\n",
    "\n",
    "# QAM\n",
    "qam_constellation = np.squeeze(tx.qammod(param.M))\n",
    "qamParam.kur, qamParam.kur3 = nlin.calcKur(qam_constellation)\n",
    "\n",
    "qamEffSNR = powerSweep - nlin.calcNLIN(qamParam, powerSweep, aseNoisePower, interConst, intraConst, interConstAdd, intraConstAdd)\n",
    "\n",
    "# learned constellation\n",
    "aeNlinParam = qamParam\n",
    "aeNlinParam.kur, aeNlinParam.kur3 = nlin.calcKur(np_constellation)\n",
    "\n",
    "aeEffSNR = powerSweep - nlin.calcNLIN(aeNlinParam, powerSweep, aseNoisePower, interConst, intraConst, interConstAdd, intraConstAdd)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(powerSweep, qamEffSNR, 'b')\n",
    "plt.plot(powerSweep, aeEffSNR, 'r')\n",
    "plt.plot(outPdBm, outMetrics['effSNR'], 'gx')\n",
    "plt.title('Power [dBm] VS effective SNR')\n",
    "plt.xlabel('Power [dBm]')\n",
    "plt.ylabel('effective SNR [dB]')\n",
    "plt.show()\n",
    "plt.savefig('snr.png')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "qamMI = np.zeros(np.shape(qamEffSNR))\n",
    "aeMI = np.zeros(np.shape(qamEffSNR))\n",
    "N = 10000\n",
    "\n",
    "for ii,p in enumerate(powerSweep):\n",
    "    qamMI[ii] = cu.SNRtoMI(N, qamEffSNR[ii], np.expand_dims(qam_constellation, 0))\n",
    "    aeMI[ii] = cu.SNRtoMI(N, aeEffSNR[ii], np.expand_dims(np_constellation, 0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(powerSweep, qamMI, 'b')\n",
    "plt.plot(powerSweep, aeMI, 'r')\n",
    "plt.plot(outPdBm, outMetrics['gaussMI'], 'gx')\n",
    "plt.plot(outPdBm, outMetrics['nnMI'], 'kx')\n",
    "plt.title('Power [dBm] VS MI')\n",
    "plt.xlabel('Power [dBm]')\n",
    "plt.ylabel('MI [bits]')\n",
    "plt.show()\n",
    "plt.savefig('mi.png')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
